#include <crt_util.h>

#if (TARGET_OS == OS_WIN)
_fd_t _file_snamepipe(const char* name, long rsize, long wsize, int timeout, int flags, int mode)
{
    SECURITY_DESCRIPTOR sDesc = {0};
    SECURITY_ATTRIBUTES sa = {sizeof(sa)};

    if (!InitializeSecurityDescriptor(&sDesc, SECURITY_DESCRIPTOR_REVISION)) {
        return FALSE;
    }

    if (!SetSecurityDescriptorDacl(&sDesc, TRUE, (PACL)NULL, FALSE)) {
        return FALSE;
    }

    sa.nLength = sizeof(sa);
    sa.lpSecurityDescriptor = &sDesc;
    sa.bInheritHandle = TRUE;

    _fd_t fh = FILE_INVALID_VALUE;

    DWORD fileaccess = 0;

    switch (flags & (O_RDONLY | O_WRONLY | O_RDWR)) {
    case O_RDONLY: /* read access */
        fileaccess = PIPE_ACCESS_OUTBOUND;
        break;
    case O_WRONLY: /* write access */
        fileaccess = PIPE_ACCESS_INBOUND;
        break;
    case O_RDWR: /* read and write access */
        fileaccess = PIPE_ACCESS_DUPLEX;
        break;
    default: /* error, bad flags */
        fileaccess = PIPE_ACCESS_DUPLEX;
        break;
    }

    fh = CreateNamedPipeA(
        name,                       // pipe name
        fileaccess |                // read/write access
            FILE_FLAG_OVERLAPPED,   // overlapped mode
        PIPE_TYPE_MESSAGE |         // message-type pipe
            PIPE_READMODE_MESSAGE | // message read mode
            PIPE_WAIT,              // blocking mode
        PIPE_UNLIMITED_INSTANCES,   // unlimited instances
        rsize,                      // output buffer size
        wsize,                      // input buffer size
        timeout,                    // client time-out
        &sa);                       // default security attributes

    return fh;
}

int _close_snamepipe(_fd_t fh)
{
    return DisconnectNamedPipe(fh) ? S_SUCCESS : S_ERROR;
}
_fd_t _file_cnamepipe(const char* name, int flags, int mode)
{
    _fd_t fh = FILE_INVALID_VALUE;

    while (1) {
        fh = CreateFileA(
            name,          // pipe name
            GENERIC_READ | // read and write access
                GENERIC_WRITE,
            0,                    // no sharing
            NULL,                 // default security attributes
            OPEN_EXISTING,        // opens existing pipe
            FILE_FLAG_OVERLAPPED, // default attributes
            NULL);

        if (fh != INVALID_HANDLE_VALUE)
            break;

        if (GetLastError() != ERROR_PIPE_BUSY) {
            return INVALID_HANDLE_VALUE;
        }

        if (!WaitNamedPipeA(name, 5000)) {
            return INVALID_HANDLE_VALUE;
        }
    }

    DWORD dwMode = PIPE_READMODE_MESSAGE;
    BOOL fSuccess = SetNamedPipeHandleState(
        fh,      // pipe handle
        &dwMode, // new pipe mode
        NULL,    // don't set maximum bytes
        NULL);   // don't set maximum time

    if (!fSuccess) {
        return INVALID_HANDLE_VALUE;
    }

    return fh;
}
int _close_cnamepipe(_fd_t fh)
{
    return _file_close(fh);
}
_fd_t _file_open(const basic_tchar* filepath, int flags, int mode)
{
    _fd_t fh = FILE_INVALID;

    DWORD fileaccess = 0, fileshare = 0, filecreate = 0, fileattr = 0;

    switch (flags & (O_RDONLY | O_WRONLY | O_RDWR)) {
    case O_RDONLY: /* read access */
        fileaccess = GENERIC_READ;
        break;
    case O_WRONLY: /* write access */
        fileaccess = GENERIC_WRITE;
        if ((flags & O_APPEND) != 0)
            fileaccess |= FILE_APPEND_DATA;
        break;
    case O_RDWR: /* read and write access */
        fileaccess = GENERIC_READ | GENERIC_WRITE;
        if ((flags & O_APPEND) != 0)
            fileaccess = GENERIC_READ | FILE_APPEND_DATA;
        break;
    default: /* error, bad flags */
        return FILE_INVALID;
    }

    /* decode open/create method flags */
    switch (flags & (O_CREAT | O_EXCL | O_TRUNC)) {
    case 0:
    case O_EXCL: /* ignore EXCL w/o CREAT */
        filecreate = OPEN_EXISTING;
        break;
    case O_CREAT:
        filecreate = OPEN_ALWAYS;
        break;
    case O_CREAT | O_EXCL:
    case O_CREAT | O_TRUNC | O_EXCL:
        filecreate = CREATE_NEW;
        break;
    case O_TRUNC:
    case O_TRUNC | O_EXCL: /* ignore EXCL w/o CREAT */
        filecreate = TRUNCATE_EXISTING;
        break;
    case O_CREAT | O_TRUNC:
        filecreate = CREATE_ALWAYS;
        break;
    default:
        return FILE_INVALID;
    }

    fileshare |= FILE_SHARE_READ | FILE_SHARE_WRITE;
    fileattr = FILE_ATTRIBUTE_NORMAL;

    fh = CreateFileW(filepath, fileaccess, fileshare, NULL, filecreate, fileattr, NULL);
    return fh;
}
int _file_close(_fd_t fh)
{
    return CloseHandle(fh) ? 0 : -1;
}
_fd_size _file_lseek(_fd_t fh, _off_t offset, int whence)
{
    LARGE_INTEGER li;
    DWORD method;
    if (whence == SEEK_CUR)
        method = FILE_CURRENT;
    else if (whence == SEEK_SET)
        method = FILE_BEGIN;
    else if (whence == SEEK_END)
        method = FILE_END;
    else {
        return -1;
    }

    li.QuadPart = offset;
    li.LowPart = SetFilePointer(fh, li.LowPart, &li.HighPart, method);

    if (li.LowPart == 0xFFFFFFFF) {
        li.QuadPart = 0;
    }
    return (li.QuadPart & INT64_MAX);
}
_fd_len _file_read(_fd_t fh, void* buf, size_t size)
{
    DWORD nRead = 0;
    if (!ReadFile(fh, buf, (DWORD)size, &nRead, NULL))
        return 0;

    return nRead;
}
_fd_len _file_write(_fd_t fh, const void* buf, size_t size)
{
    DWORD nWritten = 0;
    if (!WriteFile(fh, buf, (DWORD)size, &nWritten, NULL))
        return 0;

    return size - nWritten;
}
int64_t _file_fsize(_fd_t fh)
{
    DWORD nLow, nHigh;
    int64_t n;
    nLow = GetFileSize(fh, &nHigh);
    if (nLow == 0xFFFFFFFF)
        return 0;
    n = nHigh;
    return nLow + (n << 32);
}
int _file_delete(const basic_tchar* filepath)
{
    return DeleteFile(filepath) == TRUE ? S_SUCCESS : S_ERROR;
}
int _file_unlink(const char* filepath)
{
    return DeleteFileA(filepath) == TRUE ? S_SUCCESS : S_ERROR;
}

#elif (TARGET_OS == OS_POSIX)

_fd_t _file_snamepipe(const char* name, long rsize, long wsize, int timeout, int flags, int mode)
{
    unlink(name);

    if (-1 == mkfifo(name, mode)) {
        return FILE_INVALID_VALUE;
    }
    return open(name, flags, mode);
}

int _close_snamepipe(_fd_t fh)
{
    return _file_close(fh);
}
_fd_t _file_cnamepipe(const char* name, int flags, int mode)
{
    return _file_open(name, flags, mode);
}
int _close_cnamepipe(_fd_t fh)
{
    return _file_close(fh);
}
_fd_t _file_open(const basic_tchar* filepath, int flags, int mode)
{
    return open(filepath, flags, mode);
}
int _file_close(_fd_t fh)
{
    return close(fh);
}
_fd_size _file_lseek(_fd_t fh, _off_t offset, int whence)
{
    return lseek(fh, offset, whence);
}
_fd_len _file_read(_fd_t fh, void* buf, size_t size)
{
    return read(fh, buf, size);
}
_fd_len _file_write(_fd_t fh, const void* buf, size_t size)
{
    ssize_t len = write(fh, buf, size);

    return size - len;
}
int64_t _file_fsize(_fd_t fh)
{
    struct stat sbuf;
    if (fstat(fh, &sbuf) == -1)
        return FILE_INVALID_VALUE;
    return sbuf.st_size;
}

int _file_delete(const basic_tchar* filepath)
{
    return unlink(filepath) == 0 ? S_SUCCESS : S_ERROR;
}
int _file_unlink(const char* filepath)
{
    return unlink(filepath) == 0 ? S_SUCCESS : S_ERROR;
}

#elif (TARGET_OS == OS_MACH)

_fd_t _file_snamepipe(const char* name, long rsize, long wsize, int timeout, int flags, int mode)
{
    unlink(name);

    if (-1 == mkfifo(name, mode))
        return -1;

    return open(name, flags, mode);
}

int _close_snamepipe(_fd_t fh)
{
    return _file_close(fh);
}
_fd_t _file_cnamepipe(const char* name, int flags, int mode)
{
    return _file_open(name, flags, mode);
}
int _close_cnamepipe(_fd_t fh)
{
    return _file_close(fh);
}
_fd_t _file_open(const basic_tchar* filepath, int flags, int mode)
{
    return open(filepath, flags, mode);
}
int _file_close(_fd_t fh)
{
    return close(fh);
}
_fd_size _file_lseek(_fd_t fh, _off_t offset, int whence)
{
    return lseek(fh, offset, whence);
}
_fd_len _file_read(_fd_t fh, void* buf, size_t size)
{
    return read(fh, buf, size);
}
_fd_len _file_write(_fd_t fh, const void* buf, size_t size)
{
    ssize_t len = write(fh, buf, size);
    return size - len;
}
int64_t _file_fsize(_fd_t fh)
{
    struct stat sbuf;
    if (fstat(fh, &sbuf) == -1)
        return FILE_INVALID_VALUE;
    return sbuf.st_size;
}

int _file_delete(const basic_tchar* filepath)
{
    return unlink(filepath) == 0 ? S_SUCCESS : S_ERROR;
}
int _file_unlink(const char* filepath)
{
    return unlink(filepath) == 0 ? S_SUCCESS : S_ERROR;
}

#elif (TARGET_OS == OS_UNIX)

_fd_t _file_snamepipe(const char* name, long rsize, long wsize, int timeout, int flags, int mode)
{
    unlink(name);

    if (-1 == mkfifo(name, mode))
        return -1;

    return open(name, flags, mode);
}

int _close_snamepipe(_fd_t fh)
{
    return _file_close(fh);
}
_fd_t _file_cnamepipe(const char* name, int flags, int mode)
{
    return _file_open(name, flags, mode);
}
int _close_cnamepipe(_fd_t fh)
{
    return _file_close(fh);
}
_fd_t _file_open(const basic_tchar* filepath, int flags, int mode)
{
    return open(filepath, flags, mode);
}
int _file_close(_fd_t fh)
{
    return close(fh);
}
_fd_size _file_lseek(_fd_t fh, _off_t offset, int whence)
{
    return lseek(fh, offset, whence);
}
_fd_len _file_read(_fd_t fh, void* buf, size_t size)
{
    return read(fh, buf, size);
}
_fd_len _file_write(_fd_t fh, const void* buf, size_t size)
{
    ssize_t len = write(fh, buf, size);
    return size - len;
}
int64_t _file_fsize(_fd_t fh)
{
    struct stat sbuf;
    if (fstat(fh, &sbuf) == -1)
        return FILE_INVALID_VALUE;
    return sbuf.st_size;
}

int _file_delete(const basic_tchar* filepath)
{
    return unlink(filepath) == 0 ? S_SUCCESS : S_ERROR;
}
int _file_unlink(const char* filepath)
{
    return unlink(filepath) == 0 ? S_SUCCESS : S_ERROR;
}

#endif
